Skip to content

[#521] Targeted notifications for token holders#527

Merged
realproject7 merged 3 commits intomainfrom
task/521-targeted-notifications
Mar 25, 2026
Merged

[#521] Targeted notifications for token holders#527
realproject7 merged 3 commits intomainfrom
task/521-targeted-notifications

Conversation

@realproject7
Copy link
Copy Markdown
Owner

Summary

  • DB migration (00022): adds wallet_address to notification_tokens, creates notification_queue and token_price_snapshots tables
  • Targeted new plot notifications: notifyNewPlot() now queries on-chain balanceOf to find storyline token holders, sends only to them (falls back to all users if no holders found)
  • Price change alerts: checkPriceChangeAlert() compares current price to last snapshot, sends alerts to holders when >10% movement detected
  • Price alerts cron: new /api/cron/price-alerts route iterates active storylines and checks for price changes
  • Wallet address capture: save-token API now accepts walletAddress parameter to link FID → wallet

Architecture

  • Token holder targeting uses multicall balanceOf against all enabled notification tokens with wallet addresses
  • Price snapshots stored in token_price_snapshots table, compared against previous snapshot each cron run
  • notification_queue table created for future batched delivery (currently direct send, queue available for future optimization)
  • Bumped version to 0.1.6

Test plan

  • Migration applies cleanly (wallet_address column, new tables)
  • New plot notification targets only token holders (verify with test wallet)
  • Falls back to all users when no holders have notification tokens
  • Price alert fires when token moves >10%
  • /api/cron/price-alerts processes all active storylines
  • save-token API accepts and stores walletAddress
  • All 104 existing tests pass

Fixes #521

🤖 Generated with Claude Code

- Add wallet_address column to notification_tokens table
- Create notification_queue table for batched targeting
- Create token_price_snapshots table for price change detection
- Update notifyNewPlot to target only storyline token holders
  (falls back to all users if no holders have notification tokens)
- Add checkPriceChangeAlert for >10% price movement alerts
- Create /api/cron/price-alerts route for periodic price checking
- Accept walletAddress in save-token API route
- Bump version to 0.1.6

Fixes #521

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plotlink Ignored Ignored Mar 25, 2026 9:06am

Request Review

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST CHANGES — two findings:

1. Bug: alerts counter never incremented (price-alerts/route.ts:47)
const alerts = 0; — declared as const and never incremented, but returned in the response. Should be let alerts = 0; and incremented after each successful checkPriceChangeAlert() call. Currently the API always returns { checked: N, alerts: 0 } regardless of how many alerts fire.

2. Missing wallet address validation (save-token/route.ts)
walletAddress from the request body is passed through to the DB with no format validation. Since this is a system boundary (user input → DB), it should validate that the value is a valid Ethereum address (e.g. /^0x[0-9a-fA-F]{40}$/) before storage. Invalid values would cause silent failures in the downstream balanceOf multicall.

Otherwise solid: multicall token holder targeting with fallback is clean, migration is well-structured with proper indexes, price snapshot comparison logic is correct.

- Fix alerts counter: let + increment after checkPriceChangeAlert returns true
- checkPriceChangeAlert now returns boolean indicating if alert was sent
- Validate walletAddress as Ethereum address format before storage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The notification targeting direction is right, but the current implementation still has one behavior that broadcasts beyond holders and one trust boundary issue in the holder-mapping flow.

Findings

  • [high] The new-plot path still broadcasts to all enabled users when no holder notification tokens are found, which violates the targeted-only acceptance criteria.
    • File: lib/notifications.server.ts:272
    • Suggestion: do not fall back to getEnabledTokens() for holder-targeted plot notifications; if no subscribed holders are found, skip delivery instead of broadcasting.
  • [high] walletAddress is accepted directly from the client and persisted as the holder-mapping key without any verification, so a caller can register someone else's wallet and receive that wallet's holder-only alerts.
    • File: src/app/api/notifications/save-token/route.ts:10
    • Suggestion: derive the wallet from trusted Farcaster / miniapp context or another verified source instead of trusting raw request JSON.

Decision

Requesting changes because issue #521 explicitly requires new-plot and price-change notifications to target token holders only. The current fallback-to-all path breaks that behavior, and the unverified walletAddress input makes the targeting list spoofable.

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed — both T2b findings fixed:

  1. alerts counter now uses let and increments correctly (checkPriceChangeAlert returns boolean)
  2. walletAddress validated with /^0x[0-9a-fA-F]{40}$/ regex, invalid values silently dropped

APPROVE from T2b.

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

Re-checking against the live issue scope, the current implementation still misses two explicit targeting requirements.

Findings

  • [high] The new-plot notification path still broadcasts beyond holders.
    • File: lib/notifications.server.ts:272
    • Suggestion: remove the getEnabledTokens() fallback for holder-targeted plot notifications; if there are no subscribed holders, skip delivery instead of broadcasting.
  • [high] The holder mapping still trusts client-provided wallet identity instead of a trusted context-derived mapping.
    • File: src/app/api/notifications/save-token/route.ts:10
    • Suggestion: derive the wallet from trusted Farcaster / miniapp context as described in issue #521, rather than persisting whatever wallet the client submits.

Decision

Requesting changes because the live issue still says: targeted notifications should go to users who hold the storyline token instead of broadcasting to all users, and the suggested wallet mapping source is sdk.context.user / a trusted Farcaster mapping. The current fallback-to-all behavior and raw client wallet mapping still do not meet that scope.

- Wallet address now resolved server-side from FID via trusted Neynar API
  (verified_addresses.eth_addresses) instead of client-supplied input
- Remove broadcast fallback in notifyNewPlot — only token holders notified
- Revert client walletAddress param from save-token route

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after T2a blocker fixes. Both addressed:

  1. No broadcast fallback — notifyNewPlot returns early if no holders, no getEnabledTokens() fallback
  2. Trusted wallet — resolved server-side via Neynar verified_addresses, walletAddress removed from save-token route

APPROVE from T2b.

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Summary

The current head addresses the two in-scope blockers from my prior reviews. New-plot notifications are now holder-only with no broadcast fallback, and wallet mapping is resolved server-side from trusted Farcaster/Neynar data instead of client-supplied input.

Findings

  • None.

Decision

Approving because the current implementation now matches issue #521's targeting requirements: holder-only delivery for new-plot and price alerts, plus trusted wallet resolution for FID-to-wallet mapping.

@realproject7 realproject7 merged commit 453dd22 into main Mar 25, 2026
5 checks passed
@realproject7 realproject7 deleted the task/521-targeted-notifications branch March 25, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Targeted notifications for token holders — price alerts and new plot updates

2 participants